import axios from "../service/axios";
import uploadRequest from "axios";
import COS from "cos-js-sdk-v5";
import md5 from "md5";
type FileInfoHash = string;
type ElectronFile = File & { path: string };
class FileInfo {
    name = "";
    size = 0;
    lastModified = 0;
    type = "";
    path = "";
    PartNumber = 1;
    UploadId = "";
    Parts: Array<{ ETag: string; PartNumber: number }> = [];
    fileInfoHash = ";";
    Key = "";
    progress = 0;
    historyProgress = 0;
    showProgress = 0;
    Max = 0;
}
type options = {
    directory_id?: string;
    upload_type?: string;
}

type InitOptions = {
    onProgress?: (...args: any[]) => void;
    onFileFinish?: (...args: any[]) => void;
    onFileInfo?: (...args: any[]) => void;
};

export function getFileInfoHash(file: File) {
    let hash =
        md5(`${file.name}${file.type}${file.size}${file.lastModified}`) || "";

    return hash;
}

export default class CustomSliceFileUpload {
    static MaxFileRequestCount = 3;
    static uploadingFileCount = 0;
    static MaxSliceCount = 3;
    static uploadingFileQueue: string[] = [];
    uploadingTaskQueue: any = [];
    fileTypes = ["IMAGE", "AUDIO", "VIDEO", "OTHER"];
    fileType = "";
    thumbnails: any;
    tencentCos: any;
    config = {
        Bucket: "smartml-cd-1252627319",
        Region: "ap-chengdu",
    };
    UploadId = "";
    fielId = '';
    PartNumber = 1;
    offset = 10 * 1024 * 1024;
    Etag = "";
    Parts: any[] = [];
    startIndex = 0;
    fileInfoHash = "";
    uploadType = "";
    fileInfo: FileInfo | undefined;
    Max = 0; // 文件最大分块数量
    paused = false;
    Key = "";
    historyMaxParNumber = 1; // 历史记录最大分块数
    uploadingChunkCount = 0;
    uploadingPartNumbers: number[] = []; // 正在上传的分块
    progress = 0; // 真实进度
    showProgress = 0; // 显示进度
    historyProgress = 0;
    PartNumberProgress: any = {};
    offsetProgress = 0;
    onProgress: any;
    onFileFinish: any;
    onFileInfo: any;
    taskId = "";
    isPutObject = false;
    directory_id = '0';
    upload_type = '';
    constructor(public file: ElectronFile | FileInfoHash, options?: options) {
        this.directory_id = options && options.directory_id ? options.directory_id : '0';
        this.upload_type = options && options.upload_type ? options.upload_type : '';
        this.init();
        window.saveFileInfo = this.saveFileInfo.bind(this);
    }

    async init() {
        // this.calcMaxSliceCount();
        if (typeof this.file === "string") {
            await this.readFileInfo();
            if (this.fileInfo) {
                this.Max = Math.ceil(this.fileInfo?.size / this.offset);
            }
        } else {
            this.getFileType();
            this.getThumbnails();
            // this.getFileInfoHash();
            // this.fileInfoHash = getFileInfoHash(this.file as File);
            this.Max = Math.ceil(this.file.size / this.offset);
        }
        this.initResumeCommon();
    }

    initResumeCommon() {
        if (typeof this.file != "string") {
            if (this.file.size < this.offset) {
                this.isPutObject = true;
            } else {
                this.isPutObject = false;
            }
        } else {
            if (this.fileInfo) {
                if (this.fileInfo?.size < this.offset) {
                    this.isPutObject = true;
                } else {
                    this.isPutObject = false;
                }
            }
        }

        this.historyMaxParNumber = Math.max(
            ...this.Parts.map((item) => item.PartNumber),
            1,
        );

        let loadedPercent = this.Parts.length / this.Max;
        if (this.progress != this.showProgress) {
            // 暂停情况，实际进度大于显示进度  offsetProgress>0
            if (loadedPercent > this.showProgress) {
                this.offsetProgress = loadedPercent - this.showProgress;
                this.historyProgress = this.showProgress;
            }
        } else {
            // 刷新页面情况，实际进度小于显示进度 offsetProgress<0
            if (loadedPercent < this.showProgress) {
                this.offsetProgress = loadedPercent - this.showProgress;
                this.historyProgress = this.progress;
            }
        }
    }

    calcMaxSliceCount() {
        CustomSliceFileUpload.MaxSliceCount =
            CustomSliceFileUpload.uploadingFileCount == 0
                ? CustomSliceFileUpload.MaxFileRequestCount
                : CustomSliceFileUpload.MaxFileRequestCount /
                  CustomSliceFileUpload.uploadingFileCount;
    }
    getFileType() {
        if (typeof this.file !== "string") {
            if (this.file.type.includes("audio")) {
                this.fileType = this.fileTypes[1];
            } else if (this.file.type.includes("video")) {
                this.fileType = this.fileTypes[2];
            } else if (this.file.type.includes("image")) {
                this.fileType = this.fileTypes[0];
            } else {
                this.fileType = this.fileTypes[4];
            }
        }
    }
    saveFileInfo() {
        let common = {
            PartNumber: this.PartNumber,
            UploadId: this.UploadId,
            Parts: this.Parts,
            fileInfoHash: this.fileInfoHash,
            Key: this.Key,
            progress: this.progress,
            showProgress: this.showProgress,
            historyProgress: this.progress,
            Max: this.Max,
        };

        if (this.file && typeof this.file !== "string") {
            localStorage.setItem(
                this.fileInfoHash,
                JSON.stringify({
                    name: this.file.name,
                    size: this.file.size,
                    lastModified: this.file.lastModified,
                    type: this.file.type,
                    path: this.file.path,
                    ...common,
                }),
            );
        } else {
            if (this.file && this.fileInfo) {
                localStorage.setItem(
                    this.fileInfoHash,
                    JSON.stringify({
                        name: this.fileInfo.name,
                        size: this.fileInfo.size,
                        lastModified: this.fileInfo.lastModified,
                        type: this.fileInfo.type,
                        path: this.fileInfo.path,
                        ...common,
                    }),
                );
            }
        }
    }
    delFileInfo() {
        localStorage.removeItem(this.fileInfoHash);
        CustomSliceFileUpload.uploadingFileQueue.pop();
        localStorage.removeItem("uploadingFileQueue");
        this.file = "";
    }
    async readFileInfo() {
        let filePath = this.file as string;
        let fileStat = await window.preload.stat(filePath);
        this.fileInfoHash = getFileInfoHash(fileStat);
        let str = localStorage.getItem(this.fileInfoHash);
        let obj: any;
        if (str) {
            obj = JSON.parse(str);
        } else {
            obj = new FileInfo();
        }
        this.fileInfo = obj;
        this.startIndex = obj.PartNumber * this.offset;
        if (this.fileInfo) {
            this.PartNumber = this.fileInfo.PartNumber;
            this.UploadId = this.fileInfo.UploadId;
            this.Parts = this.fileInfo.Parts;
            this.Key = this.fileInfo.Key;
            this.historyProgress = this.fileInfo.historyProgress;
            this.progress = this.fileInfo.progress;
            this.showProgress = this.fileInfo.showProgress;
            this.Max = this.fileInfo.Max;

            // let loadedPercent = this.Parts.length / this.Max;
            // if (this.progress != this.showProgress) {
            //     if (loadedPercent > this.showProgress) {
            //         this.offsetProgress = loadedPercent - this.showProgress;
            //         this.historyProgress = this.showProgress;
            //     }
            // } else {
            //     if (loadedPercent < this.showProgress) {
            //         this.offsetProgress = loadedPercent - this.showProgress;
            //     }
            // }
        }
    }
    getThumbnails() {
        let imgUrl = "";
        if (
            this.fileType == this.fileTypes[0] ||
            this.fileType == this.fileTypes[2]
        ) {
            imgUrl = new URL(
                "@/assets/img/meadiaIndex/qita-cover.png",
                import.meta.url,
            ).href;
        } else if (this.fileType == this.fileTypes[1]) {
            imgUrl = new URL(
                "@/assets/img/meadiaIndex/audio-cover.png",
                import.meta.url,
            ).href;
        } else {
            if (this.file instanceof File) {
                const ext = this.file.name
                    .slice(this.file.name.lastIndexOf(".") + 1)
                    .toLowerCase();
                if (
                    ext == "xlsx" ||
                    ext == "xls" ||
                    ext == "xlt" ||
                    ext == "xltx" ||
                    ext == "et"
                ) {
                    imgUrl = new URL(
                        "@/assets/img/meadiaIndex/excle-cover.png",
                        import.meta.url,
                    ).href;
                } else if (
                    ext == "doc" ||
                    ext == "docx" ||
                    ext == "dot" ||
                    ext == "dotx" ||
                    ext == "wps" ||
                    ext == "rtf"
                ) {
                    imgUrl = new URL(
                        "@/assets/img/meadiaIndex/word-cover.png",
                        import.meta.url,
                    ).href;
                } else if (
                    ext == "ppt" ||
                    ext == "pptx" ||
                    ext == "dps" ||
                    ext == "pot" ||
                    ext == "pps"
                ) {
                    imgUrl = new URL(
                        "@/assets/img/meadiaIndex/ppt-cover.png",
                        import.meta.url,
                    ).href;
                } else if (ext == "pdf") {
                    imgUrl = new URL(
                        "@/assets/img/meadiaIndex/pdf-cover.png",
                        import.meta.url,
                    ).href;
                } else if (ext == "txt") {
                    imgUrl = new URL(
                        "@/assets/img/meadiaIndex/txt-cover.png",
                        import.meta.url,
                    ).href;
                } else {
                    imgUrl = new URL(
                        "@/assets/img/meadiaIndex/qita-cover.png",
                        import.meta.url,
                    ).href;
                }
            }
        }

        this.thumbnails = { imgUrl };
    }
    // 文件预上传
    async getPreFileInfo() {
        if (this.file instanceof File) {
            let preFileInfo = await axios.post(
                `/mediaslib-service/resource/fileName`,
                {
                    file_name: this.file.name,
                    size: this.file.size,
                    equipment: 2,
                    thumbnail: this.thumbnails.imgUrl,
                    directory_id: this.directory_id,
                    if_resume: this.upload_type != 'directory' && this.upload_type != 'common' // 断点判断
                },
            );
            this.Key = preFileInfo.data.result.data.oss_name;
            this.UploadId = preFileInfo.data.result.data.upload_id;
            this.fielId = preFileInfo.data.result.data.id;
        }
    }
    // 上传文件夹
    async getPreFolder() {
        let directoryName = this.file.webkitRelativePath
            .slice(0, this.file.webkitRelativePath.lastIndexOf("/"))
            .toLowerCase();
        await axios({
            url: "/mediaslib-service/resource/folder",
            method: "post",
            data: {
                parent_id: this.directory_id,
                name: directoryName
            },
        });
    }
    async getTencentToken() {
        const tencentToken: any = await axios.get(
            "/mediaresourcelib-file-manager/auth/cos/tokens",
        );
        this.tencentCos = new COS({
            getAuthorization: function (options, callback) {
                callback({
                    TmpSecretId: tencentToken.data.result.data.tmpSecret_id,
                    TmpSecretKey: tencentToken.data.result.data.tmp_secret_key,
                    SecurityToken: tencentToken.data.result.data.security_token,
                    StartTime: tencentToken.data.result.data.start_time,
                    ExpiredTime: tencentToken.data.result.data.expired_time,
                });
            },
        });
    }
    async start(initOptions?: InitOptions) {
        if (initOptions) {
            this.onProgress = initOptions.onProgress;
            this.onFileFinish = initOptions.onFileFinish;
            this.onFileInfo = initOptions.onFileInfo;
        }
        if (this.upload_type == 'directory') {
            this.getPreFolder();
        }
        await this.getPreFileInfo();
        // 断点续传
        if (this.upload_type == 'common' || this.upload_type == 'directory') {
            await this.onFileInfo({
                file: this.file,
                UploadId: this.UploadId, 
                id: this.fielId,
                ...this.thumbnails
            });
        }
        if (this.file instanceof File) {
            CustomSliceFileUpload.uploadingFileQueue.push(this.file.path);
            localStorage.setItem(
                "uploadingFileQueue",
                JSON.stringify(CustomSliceFileUpload.uploadingFileQueue),
            );
            if (!this.isPutObject) {
                this.multipartInit();
            } else {
                this.putObject();
            }
        } else {
            if (this.fileInfo) {
                if (!this.isPutObject) {
                    this.maxSliceCountMultipartUpload(
                        this.historyMaxParNumber + 1,
                    );
                } else {
                    this.putObject();
                }
            }
        }
    }
    async putObject() {
        let Body = await this.getFileBody();
        let p = 1;
        await uploadRequest(
            {
                url: `/mediaslib-service/resource/part`,
                method: 'post',
                headers: {
                    "Content-Type": "multipart/form-data"
                },
                data: {
                    upload_id: this.UploadId,
                    key: this.Key,
                    part_num: p,
                    part_size: Body.size,
                    file: Body,
                    if_last_part: true,
                    hash: md5(this.UploadId + p + Body.size)
                },
                onUploadProgress: (progressData: any) => {
                    let { progress } = progressData;
                    this.progress = progress;
                    if (!this.paused) {
                        this.showProgress = this.progress;
                    }
                    if (progress < this.historyProgress) {
                        this.onProgress &&
                            this.onProgress({
                                progress: this.historyProgress,
                                id: this.fielId
                            });
                    } else {
                        this.onProgress && this.onProgress({
                            progress: progress,
                            id: this.fielId
                        });
                    }
                }
            }
        );
    }
    async stop() {
        console.log("暂停");
        this.uploadingTaskQueue.map((item: any) => {
            if (item.taskId == this.fielId) {
                item.taskInstance();
            }
        });
        this.paused = true;
    }
    async resume() {
        console.log("继续");
        // 可能造成大量请求数，是否需要轮询查看真正上传数量
        if (this.isPutObject) {
            this.tencentCos.restartTask(this.taskId);
        } else {
            this.paused = false;
            this.initResumeCommon();
            let p = Math.max(
                ...this.uploadingPartNumbers,
                this.historyMaxParNumber,
            );
            this.maxSliceCountMultipartUpload(p + 1);
        }
    }
    async abort() {
        let res;
        if (this.isPutObject) {
            res = this.tencentCos.cancelTask(this.taskId);
        } else {
            res = await this.multipartAbort();
        }
        this.delFileInfo();
        return res;
    }

    async getFileBody() {
        let blob: any;
        this.startIndex = (this.PartNumber - 1) * this.offset;
        if (typeof this.file !== "string") {
            blob = this.file.slice(
                this.startIndex,
                this.startIndex + this.offset,
            );
            this.startIndex += this.offset;
        } else {
            if (this.fileInfo) {
                let { path, type } = this.fileInfo;
                let { start, end } = this.calcStartEnd();
                const buffer = await window.preload.createReadStream({
                    path,
                    start,
                    end,
                    type,
                });
                const uint8Array = new Uint8Array(buffer);
                blob = new Blob([uint8Array]);
            }
        }
        return blob;
    }
    calcStartEnd() {
        let endIndex = this.startIndex + this.offset - 1;
        let res = { start: this.startIndex, end: endIndex };
        this.startIndex += this.offset;
        return res;
    }
    async multipartInit() {
        this.maxSliceCountMultipartUpload();
    }
    async maxSliceCountMultipartUpload(p = this.PartNumber) {
        if (p <= this.Max) {
            for (let i = 0; i < CustomSliceFileUpload.MaxSliceCount; i++) {
                let PartNumber = p;
                if (PartNumber + i <= this.Max) {
                    await this.multipartUpload(PartNumber + i);
                }
            }
        } else {
            await this.missUpload();
        }
    }
    async missUpload() {
        for (let i = 1; i <= this.Max; i++) {
            let PartNumbers = this.Parts.map((item) => item.PartNumber);
            if (!PartNumbers.includes(i)) {
                await this.multipartUpload(i, true);
            }
        }
    }
    calcProgress(partNumber: number) {
        let sum = 0;
        for (let key in this.PartNumberProgress) {
            let value = this.PartNumberProgress[key];
            sum += value / this.Max;
        }
        this.progress = Number(sum.toFixed(4));
        if (!this.paused) {
            this.showProgress = Number(sum.toFixed(4));
        }

        // 进度
        if (this.offsetProgress < 0) {
            // console.log(this.historyProgress);
            this.onProgress &&
                this.onProgress({
                    progress: this.showProgress > this.historyProgress
                    ? this.showProgress
                    : this.historyProgress,
                    id: this.fielId
                });
        } else {
            this.onProgress && this.onProgress({
                progress: this.showProgress,
                id: this.fielId
            });
        }
    }
    async multipartUpload(p: number, missUpload = false) {
        let PartNumbers = this.Parts.map((item) => item.PartNumber);
        if (PartNumbers.includes(p)) {
            return;
        }
        this.uploadingPartNumbers.push(p);
        this.PartNumber = p;
        let Body = await this.getFileBody();
        this.uploadingChunkCount++;

        const CancelToken = uploadRequest.CancelToken;
        let cancel;
        const uploadResult = await uploadRequest(
            {
                url: `/mediaslib-service/resource/part`,
                method: 'post',
                headers: {
                    "Content-Type": "multipart/form-data"
                },
                data: {
                    upload_id: this.UploadId,
                    key: this.Key,
                    part_num: p,
                    part_size: Body.size,
                    file: Body,
                    if_last_part: p == this.Max,
                    hash: md5(this.UploadId + p + Body.size)
                },
                cancelToken: new CancelToken(function executor(c) {
                    // An executor function receives a cancel function as a parameter
                    cancel = c;
                }),
                onUploadProgress: (progressData: any) => {
                    // 上传进度
                    let { loaded, total } = progressData;
                    let rate = loaded / total;
                    this.PartNumberProgress[p] = rate;
                    this.calcProgress(p);
                }
            }
        );
        let index = this.uploadingPartNumbers.findIndex(
            (item) => item == p,
        );
        this.uploadingPartNumbers.splice(index, 1);
        // if (err) {
        //     console.log("上传失败");
        //     this.multipartUpload(p, missUpload);
        //     return;
        // }
        let CurrentPartNumbers = this.Parts.map((item) => item.PartNumber);
        if (!CurrentPartNumbers.includes(p)) {
            this.Parts.push({
                PartNumber: p,
                // ETag: data.ETag,
            });
        }
        this.uploadingChunkCount--;
        this.saveFileInfo();
        if (!missUpload) {
            if (this.PartNumber < this.Max) {
                if (!this.paused) {
                    this.PartNumber++;
                    this.multipartUpload(this.PartNumber);
                }
            }
        } 
        // 块级任务队列
        this.uploadingTaskQueue.push({
            taskId: this.fielId,
            taskInstance: cancel,
            idx: p
        });
    }
    async multipartComplete() {
        return new Promise((resolve, reject) => {
            this.tencentCos.multipartComplete(
                {
                    ...this.config,
                    Key: this.Key,
                    UploadId: this.UploadId,
                    Parts: this.Parts.sort((a, b) => {
                        return a.PartNumber - b.PartNumber;
                    }),
                },
                (err: any, data: any) => {
                    if (err) {
                        console.log(err);
                        return reject(err);
                    }
                    resolve(data);
                    console.log("Complete", data);
                    this.delFileInfo();
                },
            );
        });
    }
    async multipartAbort() {
        return new Promise((resolve, reject) => {
            this.tencentCos.multipartComplete(
                {
                    ...this.config,
                    Key: this.Key,
                    UploadId: this.UploadId,
                },
                (err: any, data: any) => {
                    if (err) {
                        reject(err);
                        return;
                    }
                    resolve(data);
                },
            );
        });
    }
}
